﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace WowTourGuideTranslator
{
    /// <summary>
    /// class that represents a guide line
    /// It can be used to parse an existing line or to create a new one 
    /// </summary>
    class GuideAction
    {
        #region declations

        /// <summary>
        /// class to represent quest objectives
        /// </summary>
        public class QuestObjective
        {
            public string Quest { get; set; }
            public string Objective { get; set; }
        }

        /// <summary>
        /// Class to represent an actions loot item
        /// </summary>
        /// <remarks>includes quantity</remarks>
        public class Loot
        {
            public int ItemId { get; set; }
            public int? Quantity { get; set; }

            #region methods

            /// <summary>
            /// Clones this instance.
            /// </summary>
            /// <returns></returns>
            public Loot Clone()
            {
                return new Loot()
                {
                    ItemId = this.ItemId,
                    Quantity = this.Quantity
                };
            }

            #endregion methods
        } 

        #endregion declations

        #region fields

        /// <summary>
        /// type of a guides action
        /// </summary>
        string actionType;

        /// <summary>
        /// the id of the action (for instance a item id for a use action as in 70_71_Howling_Fjord)
        /// </summary>
        int? id;

        /// <summary>
        /// a text that is uses wirh string.Format to format the text after the translation 
        /// for instance with a link text |cffffffff|Hitem:33221:0:0:0:0:0:0:848000716:70|h[{0}]|h|r
        /// </summary>
        string formatText;

        /// <summary>
        /// the actions text
        /// </summary>
        string actionText;

        /// <summary>
        /// simple action Tags (N,QID,O,U,PRE,Z,T)
        /// </summary>
        Dictionary<string, string> simpleTags = new SerializableDictionary<string, string>();

        /// <summary>
        /// list of quest objectives
        /// </summary>
        List<QuestObjective> questObjectives = new List<QuestObjective>();

        /// <summary>
        /// list of loot items
        /// </summary>
        List<Loot> loot = new List<Loot>();

        #endregion fields

        #region constructor

        /// <summary>
        /// Initializes a new instance of the <see cref="GuideAction"/> class from a
        /// given action line
        /// </summary>
        /// <param name="actionLine">The action line.</param>
        public GuideAction(string actionLine)
        {
            // parse text
            Match match = Regex.Match(actionLine, @"^(?<type>[\w-]) (?:(?<name>[^|]+)|(?<link>\|c[0-9a-f]{8}\|H[^:]+:(?<id>\d+)[\d:]+\|h\[(?<name>[^\]]+)\]\|h\|r))?\s*(?:(?<tags>\|.+\|)|\|?)");

            if (!match.Success)
            {
                throw new ApplicationException(string.Format("Error in action line \"{0}\"!", actionLine));
            }

            this.actionType = match.Groups[@"type"].Value;

            if (!ActionType.ActionTypes.Contains(this.actionType))
            {
                throw new ApplicationException(string.Format("Error in action line \"{0}\"! The action type \"{1}\" is invalid!", actionLine, this.actionType));
            }

            if (match.Groups[@"name"].Success)
                this.actionText = match.Groups[@"name"].Value.Trim();

            // extract link text 
            if (match.Groups[@"link"].Success)
                this.formatText = match.Groups[@"link"].Value.Replace(this.actionText, @"{0}");

            // extract id
            if (match.Groups[@"id"].Success)
                this.id = Convert.ToInt32(match.Groups[@"id"].Value);

            string tags = match.Groups[@"tags"].Value;

            if (!string.IsNullOrEmpty(tags))
            {
                // parse Tags
                MatchCollection matches = Regex.Matches(tags, @"(?:\|(?<tagtype>Q)\|(?<data>[^|]+)\|QO\|(?<qo_data>[^|]+)\||\|(?<tagtype>N|QID|O|U|PRE|Z|T|QO|Q|L|C|R)\|(?<data>[^|]*)\|?)");
                QuestObjective currentObjective = null;

                foreach (Match tagMatch in matches)
                {
                    string tagType = tagMatch.Groups["tagtype"].Value;

                    switch (tagType)
                    {
                        // no data
                        case TagType.Optional:
                        case TagType.Town:
                            this.simpleTags.Add(tagType, string.Empty);
                            break;
                        // simple data
                        case TagType.Note:
                        case TagType.QuestId:
                        case TagType.Use:
                        case TagType.Prerequsite:
                        case TagType.Zone:
                        case TagType.Class:
                        case TagType.Race:
                            this.simpleTags.Add(tagType, tagMatch.Groups["data"].Value);
                            break;
                        // quest for objective
                        case TagType.Quest:
                            // test if incomplete quest objective found
                            if (currentObjective != null &&
                                string.IsNullOrEmpty(currentObjective.Objective) &&
                                !string.IsNullOrEmpty(currentObjective.Quest))
                            {
                                throw new ApplicationException(string.Format("Error in action line \"{0}\"! Unfinished quest objective for quest \"{1}\" found before next Quest  \"{2}\"!", actionLine, currentObjective.Quest, tagMatch.Groups["data"].Value));
                            }
                            currentObjective = new QuestObjective() { Quest = tagMatch.Groups["data"].Value };
                            if (tagMatch.Groups["qo_data"].Success)
                            {
                                currentObjective.Objective = tagMatch.Groups["qo_data"].Value;
                                this.questObjectives.Add(currentObjective);
                                currentObjective = null;
                            }
                            break;
                        // objective for quest
                        case TagType.QuestObjective:
                            if (currentObjective == null ||
                                string.IsNullOrEmpty(currentObjective.Quest))
                            {
                                throw new ApplicationException(string.Format("Error in action line \"{0}\"! Unfinished quest objective \"{1}\" found without Quest !", actionLine, tagMatch.Groups["data"].Value));
                            }
                            currentObjective.Objective = tagMatch.Groups["data"].Value;
                            this.questObjectives.Add(currentObjective);
                            currentObjective = null;
                            break;
                        // Loot entry
                        case TagType.Loot:
                            {
                                Match lootMatch = Regex.Match(tagMatch.Groups["data"].Value, @"(?<id>\d+)\s?(?<cnt>\d+)?");
                                this.loot.Add(new Loot()
                                {
                                    ItemId = Convert.ToInt32(lootMatch.Groups["id"].Value),
                                    Quantity = lootMatch.Groups["cnt"].Success?Convert.ToInt32(lootMatch.Groups["cnt"].Value):1
                                });
                            }
                            break;
                        default:
                            this.simpleTags.Add(tagType, tagMatch.Groups["data"].Value);
                            break;
                    }
                } 
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="GuideAction"/> class.
        /// </summary>
        /// <param name="actionType">Type of the action.</param>
        /// <param name="actionText">The action text.</param>
        public GuideAction(string actionType, string actionText)
        {
            this.actionType = actionType;
            this.actionText = actionText;
        }

        #endregion constructor

        #region properties

        /// <summary>
        /// type of this guides action
        /// </summary>
        internal string Type
        {
            get { return actionType; }
            set { actionType = value; }
        }

        /// <summary>
        /// the actions text
        /// </summary>
        internal string Text
        {
            get { return actionText; }
            set { actionText = value; }
        }

        /// <summary>
        /// the id of the action (for instance a item id for a use action as in 70_71_Howling_Fjord)
        /// </summary>
        public int? Id
        {
            get { return id; }
            set { id = value; }
        }

        /// <summary>
        /// simple action Tags (N,QID,O,U,PRE,Z,T)
        /// </summary>
        internal Dictionary<string, string> SimpleTags
        {
            get { return simpleTags; }
            set { simpleTags = value; }
        }

        /// <summary>
        /// list of quest objectives
        /// </summary>
        internal List<QuestObjective> QuestObjectives
        {
            get { return questObjectives; }
            set { questObjectives = value; }
        }

        /// <summary>
        /// list of loot items
        /// </summary>
        internal List<Loot> LootList
        {
            get { return loot; }
            set { loot = value; }
        }

        #endregion properties

        #region methods

        /// <summary>
        /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
        /// </returns>
        public override string ToString()
        {
            StringBuilder builder = new StringBuilder();

            // base string

            // with hyperlink format
            if (!string.IsNullOrEmpty(this.formatText))
            {
                builder.AppendFormat(@"{0} ", this.actionType);
                builder.AppendFormat(this.formatText, this.actionText);
            }
            // without
            else
                builder.AppendFormat(@"{0} {1}", this.actionType, this.actionText);

            // all simple tags
            foreach (string tag in this.simpleTags.Keys)
            {
                if (string.IsNullOrEmpty(this.simpleTags[tag]))
                    builder.AppendFormat(@" |{0}|", tag);
                else
                    builder.AppendFormat(@" |{0}|{1}|", tag, this.simpleTags[tag]);
            }

            // loot tags
            foreach (Loot loot in this.loot)
            {
                if (loot.Quantity > 1)
                    builder.AppendFormat(@" |L|{0} {1}|", loot.ItemId, loot.Quantity);
                else
                    builder.AppendFormat(@" |L|{0}|", loot.ItemId);
            }

            foreach (QuestObjective obj in this.questObjectives)
            {
                builder.AppendFormat(@" |Q|{0}| |QO|{1}|", obj.Quest, obj.Objective);
            }

            return builder.ToString();
        }

        #endregion methods
    }
}
